namespace BizTalk.PipelineComponents
{
    using System;
    using System.IO;
    using System.Text;
    using System.Drawing;
    using System.Resources;
    using System.Reflection;
    using System.Diagnostics;
    using System.Collections;
    using Microsoft.BizTalk.Message.Interop;
    using Microsoft.BizTalk.Component.Interop;
    using Microsoft.BizTalk.Component;
    using Microsoft.BizTalk.Messaging;

    using NMatrix.Schematron;
    using System.Xml.XPath;
    using System.Xml.Schema;
    using System.Xml;
    using Microsoft.BizTalk.Streaming;
    using Microsoft.Win32;
    using System.Globalization;
    using NMatrix.Schematron.Formatters;
    using NMatrix.Schematron.Formatters.Serialization;
    using Microsoft.BizTalk.Component.Utilities;
    
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    [System.Runtime.InteropServices.Guid("eb08f4e7-c672-4457-9e5e-133ad00daca0")]
    [ComponentCategory(CategoryTypes.CATID_Any)]

    public class SchematronValidator : XmlValidator, IBaseComponent, IComponent, IPersistPropertyBag, IComponentUI
    {
        public const string SCHEMATRON_RESULTS_PART_NAME = "SchematronValidationResultPart";
        private static Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer btsCatalog;

        private ResourceManager resourceManager = new ResourceManager("BizTalk.PipelineComponents.SchematronValidator", Assembly.GetExecutingAssembly());

        /// <summary>
        /// Instantiates a <seealso cref="Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer"/> in order to retrieve Schemas deployed
        /// in BizTalk
        /// </summary>
        static SchematronValidator() {
            btsCatalog = new Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer();
            btsCatalog.ConnectionString = GetBizTalkManagementConnectionString();
        }

        # region Properties
        [BtsDescription("DescDocumentSpecNames")]
        [BtsPropertyName("PropDocumentSpecNames")]
        public new SchemaList DocSpecNames { get { return base.DocSpecNames; } set { base.DocSpecNames = value; } }

        [BtsDescription("DescRecoverableInterchangeProcessingValidator")]
        [BtsPropertyName("PropRecoverableInterchangeProcessingValidator")]
        public new bool RecoverableInterchangeProcessing { get { return base.RecoverableInterchangeProcessing; } set { base.RecoverableInterchangeProcessing = value; } }
        #endregion

        #region IBaseComponent members
        /// <summary>
        /// Name of the component
        /// </summary>
        [System.ComponentModel.Browsable(false)]
        public new string Name
        {
            get
            {
                return resourceManager.GetString("COMPONENTNAME", System.Globalization.CultureInfo.InvariantCulture);
            }
        }
        
        /// <summary>
        /// Version of the component
        /// </summary>
        [System.ComponentModel.Browsable(false)]
        public new string Version
        {
            get
            {
                return resourceManager.GetString("COMPONENTVERSION", System.Globalization.CultureInfo.InvariantCulture);
            }
        }
        
        /// <summary>
        /// Description of the component
        /// </summary>
        [System.ComponentModel.Browsable(false)]
        public new string Description
        {
            get
            {
                return resourceManager.GetString("COMPONENTDESCRIPTION", System.Globalization.CultureInfo.InvariantCulture);
            }
        }
        #endregion
               
        #region IComponentUI members
        /// <summary>
        /// Component icon to use in BizTalk Editor
        /// </summary>
        [System.ComponentModel.Browsable(false)]
        public new IntPtr Icon
        {
            get
            {
                return ((System.Drawing.Bitmap)(this.resourceManager.GetObject("COMPONENTICON", System.Globalization.CultureInfo.InvariantCulture))).GetHicon();
            }
        }
        
        /// <summary>
        /// The Validate method is called by the BizTalk Editor during the build 
        /// of a BizTalk project.
        /// </summary>
        /// <param name="obj">An Object containing the configuration properties.</param>
        /// <returns>The IEnumerator enables the caller to enumerate through a collection of strings containing error messages. These error messages appear as compiler error messages. To report successful property validation, the method should return an empty enumerator.</returns>
        public new System.Collections.IEnumerator Validate(object obj)
        {
            // example implementation:
            // ArrayList errorList = new ArrayList();
            // errorList.Add("This is a compiler error");
            // return errorList.GetEnumerator();
            return null;
        }
        #endregion
        
        #region IPersistPropertyBag Members

        public new void GetClassID(out Guid classID) {
            base.GetClassID(out classID);
        }

        public new void InitNew() {
            base.InitNew();
        }

        public new void Load(IPropertyBag propertyBag, int errorLog) {
            base.Load(propertyBag, errorLog);
        }

        public new void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties) {
            base.Save(propertyBag, clearDirty, saveAllProperties);
        }

        #endregion

        #region IComponent members
        /// <summary>
        /// Applies XML Schema validation and Schematron validation rules to <see cref="inMsg"/>
        /// </summary>
        /// <param name="pc">Pipeline context</param>
        /// <param name="inMsg">Input message</param>
        /// <returns>Original input message with an aditional part named SchematronValidationResultPart containing an Xml</returns>
        /// <remarks>
        /// IComponent.Execute method is used to initiate
        /// the processing of the message in this pipeline component.
        /// </remarks>
        public new IBaseMessage Execute(IPipelineContext pc, IBaseMessage inMsg) {
            ReadOnlySeekableStream seekableStream = null;
            
            // Only process non-null messages/parts
            if (null != inMsg && null != inMsg.BodyPart) {
                try {
                    seekableStream = new ReadOnlySeekableStream(inMsg.BodyPart.Data);
                    Validator val = new Validator(OutputFormatting.XML);
                    // Load Schemas
                    foreach (Microsoft.BizTalk.Component.Utilities.Schema sch in this.DocSpecNames) {
                        val.AddSchema(new XmlTextReader(new StringReader(SchematronValidator.btsCatalog.Schemas[sch.DocSpecName].XmlContent)), new BTSSchemaResolver());
                    }
                    // Apply validation
                    IXPathNavigable doc = val.Validate(XmlReader.Create(seekableStream), false);
                } catch (ValidationException ex) {
                    // Add a part to the incoming message. The part contents will be the XMLFormatter output
                    byte[] buffer = Encoding.UTF8.GetBytes(ex.Message);
                    MemoryStream m = new MemoryStream(buffer);
                    IBaseMessagePart schematronPart = pc.GetMessageFactory().CreateMessagePart();
                    schematronPart.Charset = "UTF-8";
                    schematronPart.ContentType = "text/xml";
                    schematronPart.Data = m;
                    inMsg.AddPart(SCHEMATRON_RESULTS_PART_NAME, schematronPart, false);
                    pc.ResourceTracker.AddResource(m);
                    LogError(ex.Message);
                } finally {
                    // Update body stream (Needed as we've already read the stream while validating
                    if (null != seekableStream) {
                        seekableStream.Seek(0, SeekOrigin.Begin);
                        inMsg.BodyPart.Data = seekableStream;
                        pc.ResourceTracker.AddResource(seekableStream); 
                    }
                }
            } else 
            {
                System.Diagnostics.EventLog.WriteEntry("LCMS SchemaValidator", "Incoming message is null or has no body part", EventLogEntryType.Error);
            }
            return inMsg;
        }
        #endregion

        #region Utilities
        
        private static string GetBizTalkManagementConnectionString() {
            string keyName = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BizTalk Server\3.0\Administration";
            string valueName = "MgmtDBServer";
            string str3 = "MgmtDBName";
            object obj2 = Registry.GetValue(keyName, valueName, null);
            object obj3 = Registry.GetValue(keyName, str3, null);
            if ((obj2 == null) || (obj3 == null)) {
                throw new ApplicationException(string.Format(CultureInfo.CurrentCulture, "Could not establish name of server and/or database hosting BizTalk Management database (BizTalkMgmtDb). (Should be stored at \"{0}\" and \"{1}\" beneath \"{2}\".)", valueName, str3, keyName));
            }
            return string.Format(CultureInfo.CurrentCulture, "Data Source=\"{0}\";Initial Catalog=\"{1}\";Integrated Security=SSPI", new object[] { obj2, obj3 });
        }
        private static void LogError(string xmlError) {
            output outputObj = XmlFormatter.Deserialize(xmlError);
            StringBuilder sb = new StringBuilder();

            // loop through all XMLSchema errors
            if (null != outputObj.xml) {
                foreach (messageDef msg in outputObj.xml.message) {
                    sb.AppendFormat("Validation error at line {0},column {1} : {2}{3}",
                        msg.position.line,
                        msg.position.column,
                        msg.text,
                        Environment.NewLine);
                    sb.AppendLine();
                }
            }
            // loop through all schematron errors
            if (null!=outputObj.schematron) {
                foreach (outputSchema outSch in outputObj.schematron) {
                    foreach (outputSchemaPhasePattern outSchPattern in outSch.phase.pattern) {
                        foreach (outputSchemaPhasePatternRule rule in outSchPattern.rule) {
                            foreach (messageDef msg in rule.message) {
                                sb.AppendFormat("Validation error at line {0},column {1} : {2}{3}",
                                    msg.position.line,
                                    msg.position.column,
                                    msg.text,
                                    Environment.NewLine);
                                sb.AppendLine();
                            }
                        }
                    }
                } 
            }
            System.Diagnostics.EventLog.WriteEntry("SchematronValidator", sb.ToString(), EventLogEntryType.Error);
        } 
        #endregion
    }
}
